package com.freedom.auth.infrastructure.extension.captcha;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.freedom.auth.infrastructure.constant.AuthCacheConstant;
import com.freedom.common.core.constant.SecurityConstant;
import com.freedom.common.core.enums.AuthResultCodeEnum;
import com.freedom.common.framework.exception.ServiceException;
import com.freedom.common.redis.service.RedisService;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 验证码授权模式授权者
 *
 */
public class CaptchaTokenGranter extends AbstractTokenGranter {

    /**
     * 声明授权者 CaptchaTokenGranter 支持授权模式 captcha
     * 根据接口传值 grant_type = captcha 的值匹配到此授权者
     * 匹配逻辑详见下面的两个方法
     *
     * @see CompositeTokenGranter#grant(String, TokenRequest)
     * @see AbstractTokenGranter#grant(String, TokenRequest)
     */
    private static final String GRANT_TYPE = "captcha";
    private final AuthenticationManager authenticationManager;
    private final RedisService redisService;

    public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
                               RedisService redisService) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
        this.redisService = redisService;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());

        // 验证码校验逻辑
        String captchaCode = parameters.get(SecurityConstant.CAPTCHA_CODE);
        Assert.isTrue(StrUtil.isNotBlank(captchaCode), () -> new ServiceException(AuthResultCodeEnum.CAPTCHA_CODE_NOT_BLANK));

        String uuid = parameters.get(SecurityConstant.UUID);
        String captchaCodeKey = String.format(AuthCacheConstant.CAPTCHA_CODE_KEY, uuid);

        // 从缓存取出正确的验证码和用户输入的验证码比对
        String cacheCaptchaCode = (String) redisService.get(captchaCodeKey);
        Assert.isTrue(StrUtil.isNotBlank(cacheCaptchaCode), () -> new ServiceException(AuthResultCodeEnum.CAPTCHA_CODE_TIME_OUT));
        Assert.isTrue(captchaCode.equals(cacheCaptchaCode), () -> new ServiceException(AuthResultCodeEnum.CAPTCHA_CODE_ERROR));

        // 移除后续无用参数
        parameters.remove(SecurityConstant.PASSWORD);
        parameters.remove(SecurityConstant.CAPTCHA_CODE);
        parameters.remove(SecurityConstant.UUID);

        String username = parameters.get(SecurityConstant.ACCOUNT);
        String password = parameters.get(SecurityConstant.PASSWORD);

        try {
            Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
            ((AbstractAuthenticationToken) userAuth).setDetails(parameters);

            userAuth = this.authenticationManager.authenticate(userAuth);
            if (Objects.nonNull(userAuth) && userAuth.isAuthenticated()) {
                OAuth2Request oAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
                return new OAuth2Authentication(oAuth2Request, userAuth);
            } else {
                throw new ServiceException(AuthResultCodeEnum.LOGIN_PARAM_ERROR);
            }
        } catch (AccountStatusException var8) {
            throw new ServiceException(AuthResultCodeEnum.ACCOUNT_ERROR);
        } catch (BadCredentialsException var9) {
            throw new ServiceException(AuthResultCodeEnum.LOGIN_PARAM_ERROR);
        }
    }
}
